/* Copyright (c) 2025 Beijing Semidrive Technology Corporation
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "arm_gic.h"
#include "RegHelper.h"
#include <Compiler.h>
#include "OsIf.h"
#include <register.h>
#include "Semag.h"
#include "spinlock.h"


/* interrupt max num */
static uint32 g_gic_max_intid = 0;

/* Redistributor base addresses for each core */
static uint32 gic_rdists[CPU_MAX_CORE_NUMBER];

static inline uint32 gic_get_rdist(void)
{
    return gic_rdists[Mcal_GetCpuIndex()];
}

/* Wait for register write pending
 * TODO: add timed wait
 */

static int gic_wait_rwp(uint32 intid)
{
    uint32 rwp_mask;
    uint32 base;
    uint32 timeout = 0UL;
    int ret = SDRV_GIC_STATUS_SUCCESS;

    if (intid < GIC_SPI_INT_BASE)
    {
        base = (gic_get_rdist() + GICR_CTLR);
        rwp_mask = BIT_1(0xFFFFFFFFU, GICR_CTLR_RWP);
    }
    else
    {
        base = GICD_CTLR;
        rwp_mask = BIT_1(0xFFFFFFFFU, GICD_CTLR_RWP);
    }

    while (readl(base) & rwp_mask)
    {
        timeout ++;
        if (timeout >= GIC_RWP_TIMEOUT) {
            ret = SDRV_GIC_STATUS_BUSY;
        }
    }

    return ret;
}

/**
 * @brief wake up gic redistributor
 *
 * clear ProcessorSleep and wait till ChildAsleep is cleared.
 * ProcessSleep to be cleared only when ChildAsleep is set.
 * Check if redistributor is not powered already.
 *
 * @param[in] rdist redistributor base address.
 * @return status code of operation.
 */
static int gicv3_rdist_enable(uint32 rdist)
{
    uint32 timeout = 0;

    if (!(readl(rdist + GICR_WAKER) & BIT_1(0xFFFFFFFFU, GICR_WAKER_CA))) {
        return SDRV_GIC_STATUS_SUCCESS;
    }

    /* Clear ProcessorSleep
     * Writing 0 indicates that the target is not in Processor Sleep state.
     * This is done during initialization.
     */
    RMWREG32(rdist + GICR_WAKER, GICR_WAKER_PS, 1U, 0U);

    while (readl(rdist + GICR_WAKER) & BIT_1(0xFFFFFFFFU, GICR_WAKER_CA)) {
        timeout ++;
        if (timeout >= GIC_WAKER_TIMEOUT) {
            return SDRV_GIC_STATUS_BUSY;
        }
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief initialize the cpu interface
 *
 * The API should be called by the each core.
 *
 * @param[in] rdist redistributor base address.
 * @return status code of operation.
 */
static int gicv3_cpuif_init(void)
{
    uint32 icc_sre;
    uint32 intid;
    uint32 base = gic_get_rdist() + GICR_SGI_BASE_OFF;

    /* disable all sgi and ppi */
    writel(BIT_MASK(GIC_NUM_INTR_PER_REG), ICENABLER(base, 0U));

    /* any sgi/ppi intid ie. 0-31 will select GICR_CTRL */
    if (gic_wait_rwp(0U)) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* clear pending */
    writel(BIT_MASK(GIC_NUM_INTR_PER_REG), ICPENDR(base, 0U));

    /* Configure all SGIs/PPIs as group1 interrupt.
     * All interrupts will be delivered as irq.
     */
    writel(IGROUPR_VAL, IGROUPR(base, 0U));

    /* Configure default priorities for SGI 0:15 and PPI 0:15. */
    for (intid = 0; intid < GIC_SPI_INT_BASE; intid += GIC_NUM_PRI_PER_REG) {
        writel(GIC_INT_DEF_PRI_X4, IPRIORITYR(base, intid));
    }

    /* Configure PPIs as level triggered */
    writel(0U, ICFGR(base, 1U));

    /* For Cortex-R52+, SRE always enabled.
     */
    icc_sre = arm_read_iccsre();

    if (!(icc_sre & ICC_SRE_ELX_SRE_BIT)) {
        icc_sre = (icc_sre | ICC_SRE_ELX_SRE_BIT |
                   ICC_SRE_ELX_DIB_BIT | ICC_SRE_ELX_DFB_BIT);
        arm_write_iccsre(icc_sre);
    }

    /* Set the default priority mask level */
    arm_write_iccpmr(GIC_IDLE_PRIO);

    /* Allow group0 & group1 interrupts */
    arm_write_iccigrpen0(1);
    arm_write_iccigrpen1(1);

    return SDRV_GIC_STATUS_SUCCESS;
}

/* Initialize the Redistributor. This should be called by each core. */
static int gicv3_rdist_init(void)
{
    uint8 cpu;

    cpu = Mcal_GetCpuIndex();
    gic_rdists[cpu] = GIC_RDIST_BASE + Mcal_GetCpuIndex() * GIC_RDIST_OFFSET;

    return gicv3_rdist_enable(gic_get_rdist());
}

/* Initialize the Distributor. This should be called by the main core. */
static int gicv3_dist_init(void)
{
    unsigned int num_ints;
    unsigned int intid;
    unsigned int idx;
    uint32 base = GIC_DIST_BASE;

    /* get the count of SPI INTIDs
     * that supported by GIC Distributor.
     */
    num_ints = GIC_INTR_MAX_NUM;

    /* disable the distributor */
    writel(0, GICD_CTLR);
    /* wait written done */
    if (gic_wait_rwp(GIC_SPI_INT_BASE)) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* Default configuration of all SPIs */
    for (intid = GIC_SPI_INT_BASE; intid < num_ints;
         intid += GIC_NUM_INTR_PER_REG) {
        idx = intid / GIC_NUM_INTR_PER_REG;
        /* disable interrupt */
        writel(BIT_MASK(GIC_NUM_INTR_PER_REG),
               ICENABLER(base, idx));
        /* clear pending */
        writel(BIT_MASK(GIC_NUM_INTR_PER_REG),
               ICPENDR(base, idx));
        /* Set all SPIs to group1 */
        writel(IGROUPR_VAL, IGROUPR(base, idx));
    }

    /* wait for rwp on GICD */
    if (gic_wait_rwp(GIC_SPI_INT_BASE)) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* Configure default priorities for all SPIs. */
    for (intid = GIC_SPI_INT_BASE; intid < num_ints;
         intid += GIC_NUM_PRI_PER_REG) {
        writel(GIC_INT_DEF_PRI_X4, IPRIORITYR(base, intid));
    }

    /* Configure all SPIs as active high level triggered by default */
    for (intid = GIC_SPI_INT_BASE; intid < num_ints;
         intid += GIC_NUM_CFG_PER_REG) {
        idx = intid / GIC_NUM_CFG_PER_REG;
        writel(0, ICFGR(base, idx));
    }

    /* For Cortex-R52+, not support security.
     * enable the group0 & group1 interrupt.
     */
    writel((BIT_1(0xFFFFFFFFU, GICD_CTLR_ENABLE_G0)) | (BIT_1(0xFFFFFFFFU, GICD_CTLR_ENABLE_G1)), GICD_CTLR);

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief initialize gic
 *
 * initialize the gic distributor, redistributor
 * and cpu interface.
 * The API should be called by the main core.
 *
 * @return status code of operation.
 */
int arm_gic_init(void)
{
    /* initialize the distributor */
    if (gicv3_dist_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* initialize the redistributor */
    if (gicv3_rdist_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* initialize the cpu interface */
    if (gicv3_cpuif_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief initialize gic for secondary core
 *
 * initialize redistributor and cpu interface.
 * The API should be called by the other cores except main core.
 *
 * @return status code of operation.
 */
int arm_gic_secondary_init(void)
{
    /* initialize the redistributor */
    if (gicv3_rdist_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }

    /* initialize the cpu interface */
    if (gicv3_cpuif_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief gic support max interrupt id
 */
uint32 arm_gic_suppot_max_intid(void)
{
    if (g_gic_max_intid == 0U) {
        g_gic_max_intid = GICD_TYPER_SPIS(readl(GICD_TYPER));
    }

    return g_gic_max_intid;
}

/**
 * @brief set interrupt priority mask
 *
 * @param[in] mask the set priority.
 * @return previous priority.
 */
uint32 arm_gic_irq_mask(uint32 mask)
{
    /* previous priority mask */
    uint32 old_mask = (arm_read_iccpmr() >> ICC_PMR_PRI_SHIFT);

    arm_write_iccpmr(mask << ICC_PMR_PRI_SHIFT);

    return old_mask;
}

/**
 * @brief umask all interrupt priority
 */
void arm_gic_irq_unmask(void)
{
    arm_write_iccpmr(GIC_IDLE_PRIO);
}

/**
 * @brief set interrupt type
 *
 * @param[in] intid the interrupt id.
 * @param[in] irq_type the interrupt type.
 */
void arm_gic_set_irq_type(uint32 intid, uint8_t irq_type)
{
    uint32 value;
    uint32 base = GET_DIST_BASE(intid);
    uint32 idx = intid / GIC_NUM_INTR_PER_REG;
    uint32 shift;

    /* support PPI & SPI set interrupt type */
    if (!GIC_IS_SGI(intid)) {
        irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
        /* BIT[2F+1:2F], 2F is RES0 */
        idx = intid / GIC_NUM_CFG_PER_REG;
        shift = (intid & (GIC_NUM_CFG_PER_REG - 1)) * 2;

        value = readl(ICFGR(base, idx));
        value &= ~(GICD_ICFGR_MASK << shift);
        if (irq_type & IRQ_TYPE_EDGE) {
            /* BIT[2F+1] is 1, edge-triggered */
            value |= (GICD_ICFGR_TYPE << shift);
        }

        writel(value, ICFGR(base, idx));
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
    }
}

/**
 * @brief set interrupt priority
 *
 * @param[in] intid the interrupt id.
 * @param[in] priority the interrupt priority.
 */
void arm_gic_irq_set_priority(uint32 intid, uint32 priority)
{
    uint32 base = GET_DIST_BASE(intid);

    irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
    /* PRIORITYR registers provide byte access */
    writeb(((priority & GIC_PRI_MASK) << GICD_IPRIORITY_SHIFT),
            IPRIORITYR(base, intid));
    semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
}

/**
 * @brief get interrupt priority
 *
 * @param[in] intid the interrupt id.
 * @return the interrupt priority.
 */
int arm_gic_irq_get_priority(uint32 intid)
{
    uint32 base = GET_DIST_BASE(intid);
    uint8_t priority;

    irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
    priority = readb(IPRIORITYR(base, intid));
    priority >>= GICD_IPRIORITY_SHIFT;
    priority &= GIC_PRI_MASK;
    semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);

    return priority;
}

/**
 * @brief get current active interrupt priority
 *
 * @return the interrupt priority.
 */
uint32 arm_gic_irq_get_current_priority(void)
{
    return (uint32)(GIC_PRI_MASK & (arm_read_iccrpr() >> ICC_PMR_PRI_SHIFT));
}

/**
 * @brief enable interrupt
 *
 * @param[in] intid the interrupt id.
 */
void arm_gic_irq_enable(uint32 intid)
{
    uint32 mask = BIT_1(0xFFFFFFFFU, intid & (GIC_NUM_INTR_PER_REG - 1));
    uint32 idx = intid / GIC_NUM_INTR_PER_REG;
    uint32 base = GET_DIST_BASE(intid);

    irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
    writel(mask, ISENABLER(base, idx));

    /* Affinity routing should be enabled when GIC distributor is initialized,
     * so need to set SPI's affinity, now set it to be the PE on which it is enabled.
     */
    if (GIC_IS_SPI(intid)) {
        writel(Mcal_GetCpuIndex(), IROUTER(base, intid));
    }
    semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
}

/**
 * @brief disable interrupt
 *
 * @param[in] intid the interrupt id.
 * @return status code of operation.
 */
int arm_gic_irq_disable(uint32 intid)
{
    int ret = SDRV_GIC_STATUS_SUCCESS;
    uint32 mask = BIT_1(0xFFFFFFFF, intid & (GIC_NUM_INTR_PER_REG - 1));
    uint32 idx = intid / GIC_NUM_INTR_PER_REG;

    irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
    writel(mask, ICENABLER(GET_DIST_BASE(intid), idx));
    semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
    /* poll to ensure write is complete */
    if (gic_wait_rwp(intid)) {
        ret = SDRV_GIC_STATUS_BUSY;
    }

    return ret;
}

/**
 * @brief get active interrupt id of group1
 *
 * @return the interrupt id.
 */
uint32 arm_gic_irq_ack(void)
{
    uint32 intid = arm_read_icciar1();

    return intid & GICC_IAR_INT_ID_MASK;
}

/**
 * @brief get active interrupt id of group0
 *
 * @return the interrupt id.
 */
uint32 arm_gic_fiq_ack(void)
{
    /* pending -> active/ap or ap->ap */
    uint32 intid = arm_read_icciar0();

    return intid & GICC_IAR_INT_ID_MASK;
}

/**
 * @brief end of interrupt for group1
 *
 * @param[in] intid the interrupt id.
 */
void arm_gic_irq_eoi(uint32 intid)
{
    /* Interrupt request deassertion from peripheral to GIC happens
     * by clearing interrupt condition by a write to the peripheral
     * register. It is desired that the write transfer is complete
     * before the core tries to change GIC state from 'AP/Active' to
     * a new state on seeing 'EOI write'.
     * Since ICC interface writes are not ordered against Device
     * memory writes, a barrier is required to ensure the ordering.
     * The dsb will also ensure *completion* of previous writes with
     * DEGICE nGnRnE attribute.
     */
    DSB;

    /* (AP -> Pending) Or (Active -> Inactive) or (AP to AP) nested case */
    arm_write_icceoir1(intid);
}

/**
 * @brief raise sgi
 *
 * @param[in] sgi_id the sgi interrupt id.
 * @param[in] target_list the bitmask of multi-target.
 * @return status code of operation.
 */
int arm_gic_raise_sgi(uint32 sgi_id, uint16 target_list)
{
    uint32 target_aff;
    uint32 cur_mpidr;
    uint32 aff3, aff2, aff1;
    uint64_t sgi_val;

    if (GIC_IS_SGI(sgi_id)) {
        cur_mpidr = arm_read_mpidr();
        target_aff = MPID_TO_CLUSTER_ID(cur_mpidr);
        /* Extract affinity fields from target */
        aff1 = MPIDR_AFFLVL(target_aff, 1);
        aff2 = MPIDR_AFFLVL(target_aff, 2);
        aff3 = 0;

        sgi_val = GICV3_SGIR_VALUE(aff3, aff2, aff1, sgi_id,
                        SGIR_IRM_TO_AFF, target_list);

        DSB;
        arm_write64_iccsgi1r(sgi_val);

        return SDRV_GIC_STATUS_SUCCESS;
    } else {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }
}

/**
 * @brief set interrupt route
 *
 * @param[in] intid the interrupt id.
 * @param[in] target the core id of target.
 * @return status code of operation.
 */
int arm_gic_set_affinity(uint32 target, uint32 intid)
{
    /* Affinity routing should be enabled when GIC distributor is initialized,
     * so need to set SPI's affinity, now set it to be the PE on which it is enabled.
     */
    if (GIC_IS_SPI(intid)) {
        irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
        writel(target, IROUTER(GET_DIST_BASE(intid), intid));
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
        return SDRV_GIC_STATUS_SUCCESS;
    } else {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }
}

/**
 * @brief set interrupt as group0 or group1
 *
 * @param[in] intid the interrupt id.
 * @param[in] group group0 or group1.
 * @return status code of operation.
 */
int arm_gic_set_group(uint32 intid, uint16 group)
{
    uint32 base = GET_DIST_BASE(intid);
    uint32 idx = intid / GIC_NUM_INTR_PER_REG;
    uint32 mask = 1UL << (intid & (GIC_NUM_INTR_PER_REG - 1U));

    irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
    if (group == IRQ_GROUP0) {
        writel(readl(IGROUPR(base, idx)) & (~mask), IGROUPR(base, idx));
    } else if (group == IRQ_GROUP1) {
        writel(readl(IGROUPR(base, idx)) | mask, IGROUPR(base, idx));
    } else {
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }
    semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief enable group0 or group1 interrupt
 *
 * @param[in] group group0 or group1.
 * @return status code of operation.
 */
int arm_gic_enable_group_interrupt(uint16 group)
{
    uint32 ctrl_val;

    if (!(group & (IRQ_SPI_GROUP0 | IRQ_SPI_GROUP1 |
                   IRQ_GROUP0 | IRQ_GROUP1))) {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    if (group & (IRQ_SPI_GROUP0 | IRQ_SPI_GROUP1)) {
        irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
        ctrl_val = readl(GICD_CTLR);

        if (group & IRQ_SPI_GROUP0) {
            ctrl_val |= (1UL << GICD_CTLR_ENABLE_G0);
        }

        if (group & IRQ_SPI_GROUP1) {
            ctrl_val |= (1UL << GICD_CTLR_ENABLE_G1);
        }

        writel(ctrl_val, GICD_CTLR);
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
    }

    if (group & IRQ_GROUP0) {
        arm_write_iccigrpen0(1);
    }

    if (group & IRQ_GROUP1) {
        arm_write_iccigrpen1(1);
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief disable group0 or group1 interrupt
 *
 * @param[in] group group0 or group1.
 * @return status code of operation.
 */
int arm_gic_disable_group_interrupt(uint16 group)
{
    uint32 ctrl_val;

    if (!(group & (IRQ_SPI_GROUP0 | IRQ_SPI_GROUP1 |
                   IRQ_GROUP0 | IRQ_GROUP1))) {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    if (group & (IRQ_SPI_GROUP0 | IRQ_SPI_GROUP1)) {
        irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
        ctrl_val = readl(GICD_CTLR);

        if (group & IRQ_SPI_GROUP0) {
            ctrl_val &= ~(1UL << GICD_CTLR_ENABLE_G0);
        }

        if (group & IRQ_SPI_GROUP1) {
            ctrl_val &= ~(1UL << GICD_CTLR_ENABLE_G1);
        }

        writel(ctrl_val, GICD_CTLR);
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
        if (gic_wait_rwp(GIC_SPI_INT_BASE)) {
            return SDRV_GIC_STATUS_BUSY;
        }
    }

    if (group & IRQ_GROUP0) {
        arm_write_iccigrpen0(0);
    }

    if (group & IRQ_GROUP1) {
        arm_write_iccigrpen1(0);
    }

    return SDRV_GIC_STATUS_SUCCESS;
}



/**
 * @brief set interrupt priority group
 *
 * The priority is divided into group-priority and sub-priority,
 * and group-priority can be used for preemption.
 *
 * @param[in] group group0 or group1.
 * @param[in] pri priority split point.
 * @return status code of operation.
 */
int arm_gic_set_priority_group(uint16 group, uint16 pri)
{
    if(pri < IRQ_PRI_5_BITS_GROUP || pri > IRQ_PRI_0_BITS_GROUP) {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    if (group == IRQ_GROUP0) {
        arm_write_iccbpr0(pri + 1U);
    } else if (group == IRQ_GROUP1) {
        arm_write_iccbpr1(pri + 1U);
    } else {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief raise spi
 *
 * @param[in] spi_id spi interrupt id.
 * @return status code of operation.
 */
int arm_gic_raise_spi(uint32 spi_id)
{
    uint32 base;
    uint32 idx;
    uint32 mask;

    if (GIC_IS_SPI(spi_id)) {
        irq_state_t state = semag_spin_lock_irqsave(SDRV_SEMAG_GIC);
        base = GET_DIST_BASE(spi_id);
        idx = spi_id / GIC_NUM_INTR_PER_REG;
        mask = 1UL << (spi_id & (GIC_NUM_INTR_PER_REG - 1U));

        writel(mask, ISPENDR(base, idx));
        semag_spin_unlock_irqrestore(SDRV_SEMAG_GIC, state);
    } else {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief check irq log status
 *
 * check irq log status for spi interrupt.
 *
 * @param[in] spi_id spi interrupt id.
 * @return the interrupt if valid or not.
 */
int irq_log_check_status(uint32 spi_id)
{
    uint32 base;
    uint32 idx;
    uint32 mask;

    if (GIC_IS_SPI(spi_id)) {
        idx = (spi_id / GIC_NUM_INTR_PER_REG) - 1U;
        mask = 1UL << (spi_id & (GIC_NUM_INTR_PER_REG - 1U));
        base = APB_IRQ_LOG_BASE + (idx * 4U);

        if (readl(base) & mask) {
            return SDRV_GIC_STATUS_SUCCESS;
        } else {
            return SDRV_GIC_STATUS_INVALID_IRQ;
        }
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

